#ifndef HASHTABLECORE_H_JM_20120826
#define HASHTABLECORE_H_JM_20120826

#include "Hash.h"
#include "HashRow.h"
#include "HashRowImpl.h"
#include <memory>
#include <utility>

namespace jm4bbx {
	/**
	* Sablona pre hashovaciu tabulku. Samotna tabulka ako celok nie je thread-safe 
	* ale riadky v tabulke su thread-safe. Co je postacujuce nakolko velkost 
	* tabulky je pevne dana sablonovym parametrom a jedine co sa dynamicky meni su 
	* obsahy riadkov. Toto riesenie je rychlejsie ako zamykat celu hashovaciu 
	* tabulku pri pristupre k nej. Zamykaju sa len riadky a teda k cakaniu dojde 
	* len v propade ze dojde k pristupu k totoznemu riadku. Praca s odlisnymi 
	* radkami moze prebiehat parallelne.
	* 
	* @tparam KeyT typ kluca v tabulke
	* @tparam ValueT typ hodnot
	* @tpara, SIZE velkost tabulky
	**/
	template<class KeyT, class ValueT, size_t SIZE>
	class HashTableCore {
	public:
		/// Typ kluca v tabulke.
		typedef KeyT KeyType;
		/// Typ hodnoty uskaldnenej v tabulke.
		typedef ValueT ValueType;
		/// Hashovana polozka v tabulke. Pouziva sa dvojica (kluc, hodnota) aby bolo
		/// mozne zistit kluc pri koliziach.
		typedef std::pair<KeyType, ValueType> HashEntry;
		/// Typ riadku tabulky.
		typedef HashRowImpl<HashEntry> HashRowImplType;
		typedef HashRow<HashEntry> HashRowType;
		typedef typename HashRowType::ExtendedValueType ExtendedHashEntry;

		typedef typename HashRowType::ForechValueFunType ForechValueFunType;
		typedef typename HashRowType::ForechExtendedValueFunType ForechExtendedValueFunType;
		/// Typ ukazovatela na funktor poskytujuci samotnu hashovaciu funkciu.
		typedef std::shared_ptr<Hash<KeyType>> HashFunctorPtr;
		/// Velkos hashovacej tabulky.
		static const size_t TABLE_SIZE = SIZE;

		HashTableCore(const HashFunctorPtr& hash) : hash_(hash), 
			synchronized_(true) { }

		/**
		* Zahesovanie kluca na poziciu v tabulke.
		* K hashovai sa pozuziva {@link hash_}, ktory sa nastavuje v konstruktore 
		* tabulky. Pokial je takto vypocitany hash vacsi ako je velkost tabulky 
		* {@link SIZE}, tak sa pouzije operacia modulo aby nepresahoval pozadovany 
		* rozmer.
		* 
		* @param key kluc ku ktoremu sa hahom hlada pozicia v tabulke
		* @retun pozicia v tabulke
		**/
		size_t hash(const KeyType& key) const {
			size_t index = (*hash_)(key);
			//assert(index < TABLE_SIZE);
			return index % SIZE;
		}

		/**
		* Pridaj hodnotu na poziciu urcenu klucom do tabulky.
		* 
		* @param key kluc pridavanej hodnoty
		* @param value pridavana hodnota
		**/
		int add(const KeyType& key, const ValueType& value) {
			size_t index = hash(key);
			return getRow(index)->add(HashEntry(key, value));
		}

		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach leziacich 
		* v riadku, ktory je urceny predanym klucom.
		* 
		* @param key kluc identifikujuci riadok
		* @param functor akcia vykonana na hodnotach v riadku
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach 
		*         v riadku tj. akceptuje prave jednu dvojicu (kluc, hodnota) 
		*         na jedno volanie
		**/

		std::pair<int, int> foreach(const KeyType& key, const ForechValueFunType& functor, const std::pair<int, int>& iterationContext) {
			size_t index = hash(key);
			getRow(index)->foreach(functor, iterationContext);
		}

		int foreachExtended(const KeyType& key, const ForechExtendedValueFunType& functor, int iterationContext) {
			size_t index = hash(key);
			return getRow(index)->foreachExtended(functor, iterationContext);
		}
		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach v tabulke.
		* 
		* @param functor akcia vykonana na hodnotach v tabulke
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach
		*         tj. akceptuje prave jednu dvojicu (kluc, hodnota) na jedno 
		*         volanie
		**/
		
		void foreach(const ForechValueFunType& functor) {
			for (size_t i = 0; i < TABLE_SIZE; ++i) {
				getRow(i)->foreach(functor);
			}		
		}

		void foreachExtended(const ForechExtendedValueFunType& functor) {
			for (size_t i = 0; i < TABLE_SIZE; ++i) {
				getRow(i)->foreachExtended(functor);
			}		
		}

		/**
		* Vykonanie akcie definovanej funktorom na vsetkych hodnotach v tabulke 
		* pricom predavane hodnoty su doplnene indexom riadku v tabulke (hashom).
		* 
		* @param functor akcia vykonana na hodnotach v tabulke
		* 
		* @tparam T typ funktora, ktory urcuje operaciu vykonanu na hodnotach 
		*         tj. akceptuje prave dva parametre a to dvojicu (kluc, hodnota) 
		*         a ich hash na jedno volanie
		**/
		template<class T>
		void foreachIndexed(const T& functor) {
			for (size_t i = 0; i < TABLE_SIZE; ++i) {
				getRow(i)->foreach([&functor, i](HashEntry& entry) {
					functor(entry, i);
				});
			}		
		}

		/**
		* Odstranenie vsetkych hodnot zaheshovanych pod tymto klucom.
		* Pozor moze odstranit aj hodnoty ulozene pod inym klucom ale s rovnakym 
		* hashom.
		**/
		void remove(const KeyType& key) {
			size_t index = hash(key);
			getRow(index)->clear();
		}

		/**
		* Je pod zadanym klucom nieco v tabulke ulozene?
		* Pokial vrati false tak na danom mieste isto nie je nic ulozene. 
		* Pokial vrati tru tak hodnota ulozena na danom mieste nemusi zodpovedat 
		* zadanemu klucu - kolizia pri hashovani.
		**/
		bool hasKey(const KeyType& key) const {
			size_t index = hash(key);
			return !getRow(index)->isEmpty();
		}

		/**
		* Zamze vsetky zahashovane data v tabulke.
		**/
		void clear() {
			for (size_t i = 0; i < TABLE_SIZE; ++i) {
				getRow(i)->clear();
			}
		}

		bool isSynchronized() const { return synchronized_; }
		void setSynchronized(bool val) { synchronized_ = val; }
	private:
		/// Pevne dana hashovacia tabulka.
		HashRowImplType hashTable_[TABLE_SIZE];
		/// Funktor poskytujuci hashovaciu funkciu.
		HashFunctorPtr hash_;
		bool synchronized_;


		HashRowType* getRow(const size_t& index) {
			if (synchronized_) {
				return hashTable_[index].getLockableInstance();
			}
			else {
				return hashTable_[index].getLockFreeInstance();
			} 
		}

	};


} // namespace jm4bbx

#endif
